//
//  z8530.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 07/06/2019.
//  Copyright © 2019 Thomas Harte. All rights reserved.
//

#pragma once

#include <cstdint>

namespace Zilog::SCC {

/*!
	Models the Zilog 8530 SCC, a serial adaptor.
*/
class z8530 {
public:
	/*
		**Interface for emulated machine.**

		Notes on addressing below:

		There's no inherent ordering of the two 'address' lines,
		A/B and C/D, but the methods below assume:

			A/B = A0
			C/D = A1
	*/

	/// Performs a read from the SCC; see above for conventions as to 'address'.
	std::uint8_t read(int address);
	/// Performs a write to the SCC; see above for conventions as to 'address'.
	void write(int address, std::uint8_t value);
	/// Resets the SCC.
	void reset();

	/// @returns The current value of the status output: @c true for active; @c false for inactive.
	bool get_interrupt_line() const;

	struct Delegate {
		/*!
			Communicates that @c scc now has the interrupt line status @c new_status.
		*/
		virtual void did_change_interrupt_status(z8530 &, bool new_status) = 0;
	};

	/*!
		Sets the delegate for this SCC. If this is a new delegate it is sent
		an immediate did_change_interrupt_status message, to get it
		up to speed.
	*/
	void set_delegate(Delegate *const delegate) {
		if(delegate_ == delegate) return;
		delegate_ = delegate;
		delegate_->did_change_interrupt_status(*this, get_interrupt_line());
	}

	/*
		**Interface for serial port input.**
	*/
	void set_dcd(int port, bool level);

private:
	class Channel {
	public:
		uint8_t read(bool data, uint8_t pointer);
		void write(bool data, uint8_t pointer, uint8_t value);
		void set_dcd(bool level);
		bool get_interrupt_line() const;

	private:
		uint8_t data_ = 0xff;

		enum class Parity {
			Even, Odd, Off
		} parity_ = Parity::Off;

		enum class StopBits {
			Synchronous, OneBit, OneAndAHalfBits, TwoBits
		} stop_bits_ = StopBits::Synchronous;

		enum class Sync {
			Monosync, Bisync, SDLC, External
		} sync_mode_ = Sync::Monosync;

		int clock_rate_multiplier_ = 1;

		uint8_t interrupt_mask_ = 0;			// i.e. Write Register 0x1.

		uint8_t external_interrupt_mask_ = 0;	// i.e. Write Register 0xf.
		bool external_status_interrupt_ = false;
		uint8_t external_interrupt_status_ = 0;

		bool dcd_ = false;
	} channels_[2];

	uint8_t pointer_ = 0;

	uint8_t interrupt_vector_ = 0;

	uint8_t master_interrupt_control_ = 0;

	bool previous_interrupt_line_ = false;
	void update_delegate();
	Delegate *delegate_ = nullptr;
};

}
